<?php
/**
 * Cameo Library
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @category   Cameo
 * @package    Cameo_SMS
 * @subpackage Gateway
 * @copyright  Copyright (c) 2008 Sylvain Filteau (http://www.cidsphere.com)
 */

/**
 * Cameo_SMS_Gateway_Abstract
 */
require_once('Cameo/SMS/Gateway/Abstract.php');

/**
 * Cameo_SMS_Gateway_Abstract
 */
require_once('Cameo/SMS/Gateway/Exception.php');

/**
 * Class that abstracts the methods usable by the Clickatell Gateway.
 *
 * @todo describe the options for this gateway and options of the extended class
 *
 * @category   Cameo
 * @package    Cameo_SMS
 * @subpackage Gateway
 * @copyright  Copyright (c) 2008 Sylvain Filteau (http://www.cidsphere.com)
 */
abstract class Cameo_SMS_Gateway_Clickatell extends Cameo_SMS_Gateway_Abstract {

	/**
	 * Required features available for this gateway
	 */
	const FEAT_TEXT 	= 1;
	const FEAT_8BIT 	= 2;
	const FEAT_UDH 		= 4;
	const FEAT_UCS2 	= 8;
	const FEAT_ALPHA 	= 16;
	const FEAT_NUMER 	= 32;
	const FEAT_FLASH 	= 512;
	const FEAT_DELIVACK = 8192;
	const FEAT_CONCAT 	= 16384;

	/**
	 * Options to provide to the gateway
	 */
	const OPT_QUEUE 				= 'queue';
	const OPT_GATEWAY_ESCALATION 	= 'escalation';
	const OPT_API_KEY				= 'api_key';
	const OPT_API_MSG_ID			= 'apimsgid';
	const OPT_DELIV_TIME			= 'deliv_time';

	/**
	 * List of valid required features
	 *
	 * @var array
	 */
	protected static $_VALID_REQUIRED_FEATURES = array(
		self::FEAT_TEXT,
		self::FEAT_8BIT,
		self::FEAT_UDH,
		self::FEAT_UCS2,
		self::FEAT_ALPHA,
		self::FEAT_NUMER,
		self::FEAT_FLASH,
		self::FEAT_DELIVACK,
		self::FEAT_CONCAT
	);
	
	/**
	 * List of error code associated to their messages
	 *
	 * @var array
	 */
	protected static $_ERROR_CODE = array(
		'001' => 'Authentication failed',
		'002' => 'Unknown username or password',
		'003' => 'Session ID expired',
		'004' => 'Account frozen',
		'005' => 'Missing session ID',
		'007' => 'IP Lockdown violation',
		'101' => 'Invalid or missing parameters',
		'102' => 'Invalid user data header',
		'103' => 'Unknown API message ID',
		'104' => 'Unknown client message ID',
		'105' => 'Invalid destination address',
		'106' => 'Invalid source address',
		'107' => 'Empty message',
		'108' => 'Invalid or missing API ID',
		'109' => 'Missing message ID',
		'110' => 'Error with email message',
		'111' => 'Invalid protocol',
		'112' => 'Invalid message type',
		'113' => 'Maximum message parts exceeded',
		'114' => 'Cannot route message',
		'115' => 'Message expired',
		'116' => 'Invalid Unicode data',
		'120' => 'Invalid delivery time',
		'201' => 'Invalid batch ID',
		'202' => 'No batch template',
		'301' => 'No credit left',
		'302' => 'Max allowed credit',
		'606' => 'Invalid Voucher',
		'607' => 'Expired Voucher'
	);

	/**
	 * List of status code associated to their message.
	 *
	 * @var array
	 */
	protected static $_STATUS_CODE = array(
		'001' => 'Message unknown',
		'002' => 'Message queued',
		'003' => 'Delivered to gateway',
		'004' => 'Received by recipient',
		'005' => 'Error with message',
		'006' => 'User cancelled message delivery',
		'007' => 'Error delivering message',
		'008' => 'OK',
		'009' => 'Routing error',
		'010' => 'Message expired',
		'011' => 'Message queued for later delivery',
		'012' => 'Out of credit'
	);

	/**
	 * In which queue the message will be delivered. 
	 * 
	 * Must be between 1 and 3. Queue #1 is the most priority.
	 *
	 * @var int
	 */
	protected $_queue = 3;

	/**
	 * 
	 * 
	 * @var bool
	 */
	protected $_gateway_escalation = false;

	/**
	 * API Key use to communicate with the gateway
	 * 
	 * @var string
	 */
	protected $_api_key;

	/**
	 * Session key given by the server after authentification.
	 *
	 * When you first login, you just have to give the session key for the
	 * next query. It expires after some times when the gateway is not used.
	 * 
	 * @var string
	 */
	protected $_session_key = null;

	/**
	 * When the message should be delivered.
	 * 
	 * @var unknown
	 */
	protected $_deliv_time;

	/**
	 * Construct the Clickatell gateway
	 * 
	 * @param array $default_options Options to give to the gateway.
	 */
	public function __construct(array $default_options = array()) {
		parent::__construct($default_options);

		foreach ($default_options as $k => $v) {
			switch ($k) {
				case self::OPT_QUEUE:
					$this->_queue = $v;
					break;
				case self::OPT_GATEWAY_ESCALATION:
					$this->_gateway_escalation = (bool) $v;
					break;
				case self::OPT_API_KEY:
					$this->_api_key = $v;
					break;
				case self::OPT_DELIV_TIME:
					$this->_deliv_time = $v;
					break;
			}
		}
	}

	public function send(Cameo_SMS_Message $message, array $options = array()) {
		$options = $this->_getOptions($options);

		$options['to'] = $message->getTo();
		$options['from'] = $message->getFrom();
		$options['text'] = $message->getText();

		if ($message->isUnicode()) {
			$options['unicode'] = true;
		}

		$options['cliMsgId'] = $message->getUID();

		$ret = $this->_callMethod('sendmsg', $options);

		return $ret[0];
	}

	public function batch(Cameo_SMS_Message $message, array $values, array $options = array()) {
		$options = $this->_getOptions($options);
		$message->setTo(join(',', $values));

		$this->send($message, $options);
	}

	public function tokenPay($voucher) {
		$this->_callMethod('token_pay', array('token' => $voucher));
	}

	public function auth() {
		$this->_session_key = null;
		return $this->_callMethod('auth', array());
	}

	public function ping() {
		if (is_null($this->_session_key)) {
			throw new Cameo_SMS_Gateway_Exception('Cannot ping the server, you must authenticate before.');
		}

		return $this->_callMethod('ping', array('session_id' => $this->_session_key));
	}

	public function test() {
		return $this->ping();
	}

	public function getBalance() {
		return $this->_callMethod('getbalance', array());
	}

	public function queryMsg($msg_id, $is_client_id = false) {
		$k = $is_client_id ? 'climsgid' : 'apimsgid';

		return $this->_callMethod('querymsg', array($k => $msg_id));
	}

	public function delMsg($msg_id, $is_client_id = false) {
		$k = $is_client_id ? 'climsgid' : 'apimsgid';

		return $this->_callMethod('delmsg', array($k => $msg_id));
	}

	/**
	 * Get the error message by the code.
	 *
	 * @param string $code
	 * @return string if the message exist, false else.
	 */
	public function getErrorMessage($code) {
		if (!isset(self::$_ERROR_CODE[$code])) {
			return false;
		}

		return self::$_ERROR_CODE[$code];
	}

	/**
	 * Get the status message by the code.
	 *
	 * @param string $code
	 * @return string if the message exist, false else.
	 */
	public function getStatusMessage($code) {
		if (!isset(self::$_STATUS_CODE[$code])) {
			return false;
		}

		return self::$_STATUS_CODE[$code];
	}

	/**
	 * Get auth params to send command to the server.
	 * 
	 * If the server is authenticated, it just sends the session key.
	 *
	 * @return array
	 * 
	 * @throws Cameo_SMS_Gateway_Exception When you're not authenticated and you have not provided an api id
	 */
	protected function _getAuthParams() {
		$auth_params = array();
		if (is_null($this->_session_key)) {
			if (!$this->_api_key) {
				throw new Cameo_SMS_Gateway_Exception('Cannot send the message, no api key was set');
			}

			$auth_params['user'] 		= $this->_username;
			$auth_params['password'] 	= $this->_password;
			$auth_params['api_id'] 		= $this->_api_key;
		} else {
			$auth_params['session_id']	= $this->_session_key;
		}

		return $auth_params;
	}

	/**
	 * Create an exception with the error code in parameter.
	 * 
	 * The messages are stored in Cameo_SMS_Gateway_Clickatell::$_ERROR_CODE
	 * indexed by error code.
	 *
	 * @param string $code
	 * @return Cameo_SMS_Gateway_Exception
	 */
	protected function _errorException($code) {
		if (isset(self::$_ERROR_CODE[$code])) {
			return new Cameo_SMS_Gateway_Exception(self::$_ERROR_CODE[$code], $code);
		}

		return new Cameo_SMS_Gateway_Exception('Unknown error');
	}

	/**
	 * Get the options as an array to be used by the gateway.
	 * 
	 * If options are specified in parameters, it will automatically override
	 * those specified previously in th object.
	 *
	 * @param array $new_options overrides options specified previously in the object.
	 * @return array All the options.
	 */
	protected function _getOptions(array $new_options = array()) {
		$options = array();

		if ($this->_queue == 1 || $this->_queue == 2) {
			$options['queue'] = $this->_queue;
		}

		if ($this->_gateway_escalation) {
			$options['escalation'] = 1;
		}

		if ($this->_api_key) {
			$options[self::OPT_API_KEY] = $this->_api_key;
		}

		if ($this->_deliv_time) {
			$options[self::OPT_DELIV_TIME] = $this->_deliv_time;
		}

		$options = array_merge($options, parent::_getOptions($new_options));

		// The "required features" options must be summed to be sent to the gateway
		if (isset($options[Cameo_SMS_Gateway_Abstract::OPT_FEATURES_REQUIRED])) {
			$options[Cameo_SMS_Gateway_Abstract::OPT_FEATURES_REQUIRED]
				= array_sum((array) $options[Cameo_SMS_Gateway_Abstract::OPT_FEATURES_REQUIRED]);
		}

		return $options;
	}

	/**
	 * Accessor.
	 * 
	 * Add a required feature to the list. The feature must be in this list :
	 * <ul>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_TEXT (1)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_8BIT (2)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_UDH (4)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_UCS2 (8)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_ALPHA (16)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_NUMER (32)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_FLASH (512)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_DELIVACK (8192)</li>
	 * 	<li>Cameo_SMS_Gateway_Clickatell::FEAT_CONCAT (16384)</li>
	 * </ul>
	 * 
	 * You can refer to the clickatell gateway documentation for more 
	 * information about these features.
	 * 
	 * @param int $feature
	 * 
	 * @throws Cameo_SMS_Gateway_Exception When the feature is not in the list of valid feature.
	 */
	public function addRequiredFeature($feature) {
		if (!in_array($feature, self::$_VALID_REQUIRED_FEATURES)) {
			throw new Cameo_SMS_Gateway_Exception('Cannot add the feature to the gateway, the value is invalid');
		}
		
		parent::addRequiredFeature($feature);
	}
	
	/**
	 * Accessor.
	 * 
	 * Reset the list of required features the the list provided.
	 * 
	 * If $features is not an array, the item will be converted to an array.
	 * 
	 * @see Cameo_SMS_Gateway_Clickatell::addRequiredFeature()
	 *
	 * @param mixed $features
	 * 
	 * @throws Cameo_SMS_Gateway_Exception When the feature is not in the list of valid feature.
	 */
	public function setRequiredFeatures($features) {
		$features = (array) $features;
		foreach ($features as $feature) {
			if (!in_array($feature, self::$_VALID_REQUIRED_FEATURES)) {
				throw new Cameo_SMS_Gateway_Exception('Cannot add the feature to the gateway, the value is invalid');
			}
		}
	}
	
}